package com.hmall.search.service.Impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmall.api.dto.ItemDoc;
import com.hmall.api.dto.query.ItemPageQuery;
import com.hmall.common.domain.PageDTO;
import com.hmall.search.domain.FilterData;
import com.hmall.search.service.ISearchService;
import com.hmall.search.utils.EsUtil;
import com.mysql.cj.util.TimeUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author zhangyan
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SearchServiceImpl implements ISearchService {

    private final EsUtil esUtil;
    private RestHighLevelClient client;

    private final String redisKey = "search-service:categoryListAndBrandAgg";
    private final RedisTemplate<String,String> redisTemplate;

    @PostConstruct
    public void init(){
        client  = esUtil.getClient();
    }

    @Override
    public PageDTO<ItemDoc> search(ItemPageQuery query) {
        String key = query.getKey();
        String category = query.getCategory();
        String brand = query.getBrand();
        Integer minPrice = query.getMinPrice();
        Integer maxPrice = query.getMaxPrice();
        Integer pageSize = query.getPageSize();
        Boolean isAsc = query.getIsAsc();
        String sortBy = StrUtil.isNotBlank(query.getSortBy()) ? query.getSortBy() : "updateTime";

        SearchRequest searchRequest = new SearchRequest("items");
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        //搜索 关键词 如果没有 搜索全部
        QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
        if (StrUtil.isNotBlank(key)){
            queryBuilder = QueryBuilders.matchQuery("name",key);
            highlightBuilder.field("name");
            highlightBuilder.preTags("<span style='color:red'>");
            highlightBuilder.postTags("</span>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }
        boolQuery.must(queryBuilder);

        //搜索  分类 和  品牌  不分词查询
        if (StrUtil.isNotBlank(category)){
            boolQuery.filter(QueryBuilders.termQuery("category",category));
        }
        if (StrUtil.isNotBlank(brand)){
            boolQuery.filter(QueryBuilders.termQuery("brand",brand));
        }

        //价格区间
        if (ObjUtil.isAllNotEmpty(minPrice,maxPrice)){
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice));
        }


        //排序
        SortOrder sortOrder = SortOrder.DESC;
        if (ObjUtil.isAllNotEmpty(sortBy,isAsc)){
            sortOrder = isAsc ? SortOrder.ASC : SortOrder.DESC;
        }

        searchSourceBuilder.from(query.from()).size(pageSize);
        searchSourceBuilder.sort(sortBy,sortOrder);
        searchSourceBuilder.query(boolQuery);
        searchRequest.source(searchSourceBuilder);



        SearchResponse searchResponse = null;
        try {
            searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("查询失败 {}",e);
        }

        long total = Objects.requireNonNull(searchResponse.getHits().getTotalHits()).value;
        SearchHit[] hits = searchResponse.getHits().getHits();
        //高亮
        List<ItemDoc> list = Arrays.stream(hits).map(v -> {
            Map<String, HighlightField> highlightFields = v.getHighlightFields();
            String sourceAsString = v.getSourceAsString();
            ItemDoc itemDoc = JSONUtil.toBean(sourceAsString, ItemDoc.class);
            if (CollUtil.isNotEmpty(highlightFields)){
                HighlightField highlightField = highlightFields.get("name");
                if (ObjUtil.isAllNotEmpty(highlightField)){
                    Text[] fragments = highlightField.fragments();
                    if (fragments != null && fragments.length > 0){
                        String name = fragments[0].toString();
                        itemDoc.setName(name);
                    }
                }
            }
            return itemDoc;
        }).collect(Collectors.toList());
        PageDTO<ItemDoc> pageDTO = new PageDTO<>();
        pageDTO.setList(list);
        pageDTO.setTotal(total);
        return pageDTO;
    }

    @Override
    public FilterData filter(ItemPageQuery query) {
        BoundValueOperations<String, String> operations = redisTemplate.boundValueOps(redisKey);
        String filterDataJsonStr = operations.get();
        if (StrUtil.isNotBlank(filterDataJsonStr)){
            FilterData filterData = JSONUtil.toBean(filterDataJsonStr, FilterData.class);
            log.info("缓存中获取分类和品牌 : {}",filterData);
            return filterData;
        }

        SearchRequest searchRequest = new SearchRequest("items");

        //全部查询
        searchRequest.source().query(QueryBuilders.matchAllQuery());
        searchRequest.source().aggregation(AggregationBuilders.terms("categoryAgg").field("category").size(10));
        searchRequest.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(10));

        SearchResponse search = null;
        try {
            search = client.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("查询失败 {}",e);
        }
        System.out.println(search);
        Aggregations aggregations = search.getAggregations();
        Terms categoryAgg = aggregations.get("categoryAgg");
        Terms brandAgg = aggregations.get("brandAgg");
        List<? extends Terms.Bucket> categoryAggBuckets = categoryAgg.getBuckets();
        List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();
        List<String> categoryList = categoryAggBuckets.stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
        List<String> brandList = brandAggBuckets.stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
        log.info("分类和品牌 : {} , {}",categoryList,brandList);
        FilterData filterData = new FilterData(categoryList, brandList);
        filterDataJsonStr = JSONUtil.toJsonStr(filterData);
        // 生成 30~60 之间的随机分钟数
        int randomMinutes = RandomUtil.randomInt(30, 61); // 61 是开区间，相当于 [30,60]
        operations.set(filterDataJsonStr,randomMinutes, TimeUnit.MINUTES);
        return filterData;
    }
}
